package com.dreamlin.lindownload;

import android.annotation.SuppressLint;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import androidx.annotation.NonNull;

import com.dreamlin.lindownload.entity.LinTaskBean;
import com.dreamlin.lindownload.entity.LinThreadBean;
import com.dreamlin.lindownload.thread.LinRunnable;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import io.realm.Realm;

/**
 * <p> Title: LinDownloadTask </p>
 * <p> Description: </p>
 *
 * @author: dreamlin
 * @date: 2020/5/5
 * @version: V1.0.0
 * Created by dreamlin on 2020/5/5.
 */
public class LinDownloadTask implements LinTaskImp {

    private static final String TAG = "LinDownloadTask";
    private static final int MSG_START = 0x00;
    private static final int MSG_SUCCESS = 0x01;
    private static final int MSG_PROGRESS = 0x02;
    private static final int MSG_PAUSE = 0x03;
    private static final int MSG_CANCEL = 0x04;
    private static final int MSG_FAIL = 0x05;
    private static final int MSG_RETRY = 0x06;

    private LinTaskBean linTaskBean;
    private CountDownLatch countDownLatch;
    private ExecutorService executors;
    private ScheduledExecutorService scheduled;
    private List<LinRunnable> linRunnableList;
    private volatile int status = LinDownload.STATUS_NONE;
    private int autoRetry = 0;
    private long lastFinished = 0;
    private LinDownloadListener listener;

    public LinDownloadTask(@NonNull LinTaskBean linTaskBean, LinDownloadListener listener) {
        this.linTaskBean = linTaskBean;
        this.listener = listener;
        linRunnableList = new ArrayList<>();
        executors = Executors.newCachedThreadPool();
        scheduled = Executors.newScheduledThreadPool(1);
//        spiltThread();
    }

    private void spiltThread() {
        if (executors.isShutdown()) {
            executors = Executors.newCachedThreadPool();
        }
        if (scheduled.isShutdown()) {
            scheduled = Executors.newScheduledThreadPool(1);
        }
        if (linRunnableList.size() > 0)
            linRunnableList.clear();
        //处理分割线程
        executors.execute(new Runnable() {
            @Override
            public void run() {
                //先看任务是否存在
                List<LinThreadBean> threadInfoList = linTaskBean.getThreadInfoList();
                if (threadInfoList != null && threadInfoList.size() > 0) {
                    linTaskBean.setLength(threadInfoList.get(0).getLength());
                    countDownLatch = new CountDownLatch(threadInfoList.size());
                    for (int i = 0; i < threadInfoList.size(); i++) {
                        LinRunnable linRunnable = new LinRunnable(countDownLatch, threadInfoList.get(i), i);
                        executors.execute(linRunnable);
                        linRunnableList.add(linRunnable);
                    }
                } else {

                    HttpURLConnection conn = null;
                    RandomAccessFile accessFile = null;
                    try {
                        URL url = new URL(linTaskBean.getUrl());
                        conn = (HttpURLConnection) url.openConnection();
                        conn.setRequestMethod("GET");
                        conn.setConnectTimeout(10000);
                        conn.setReadTimeout(10000);

                        if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
                            int contentLength = conn.getContentLength();
                            if (contentLength > 0) {
                                //如果未设置线程数量，自动计算
                                if (linTaskBean.getThreadNum() == 0) {
                                    int threadNum = Math.min((int) (contentLength / LinDownload.MB) * 10, LinDownload.MAX_THREAD_NUMBER);
                                    linTaskBean.setThreadNum(threadNum);
                                }
                                countDownLatch = new CountDownLatch(linTaskBean.getThreadNum());
                                File path = new File(linTaskBean.getSavePath());
                                if (!path.exists()) {
                                    if (!path.mkdirs()) {
                                        throw new RuntimeException("无法创建文件路径".concat(linTaskBean.getSavePath())
                                                .concat("请检查权限或路径合法性"));
                                    }
                                }
                                File file = new File(path, linTaskBean.getFileName());
                                if (!file.exists()) {
                                    if (!file.createNewFile()) {
                                        throw new RuntimeException(linTaskBean.getFileName().concat("创建失败"));
                                    }
                                }
                                linTaskBean.setLength(contentLength);
                                accessFile = new RandomAccessFile(file, "rw");
                                accessFile.setLength(linTaskBean.getLength());
                                long length = linTaskBean.getLength() / linTaskBean.getThreadNum();
                                for (int i = 0; i < linTaskBean.getThreadNum() - 1; i++) {

                                    long start = length * i;
                                    long end = start + length - 1;
                                    LinThreadBean linThreadBean = new LinThreadBean(
                                            linTaskBean.getUrl(),
                                            linTaskBean.getSavePath(),
                                            linTaskBean.getFileName(),
                                            start, end,
                                            linTaskBean.getLength(),
                                            linTaskBean.getAutoRetry(),
                                            linTaskBean.getCallInterval()
                                    );
                                    LinRunnable linRunnable = new LinRunnable(countDownLatch, linThreadBean, i);
                                    linRunnableList.add(linRunnable);
                                    executors.execute(linRunnable);
                                }
                                long start = length * (linTaskBean.getThreadNum() - 1);
                                LinThreadBean linThreadBean = new LinThreadBean(
                                        linTaskBean.getUrl(),
                                        linTaskBean.getSavePath(),
                                        linTaskBean.getFileName(),
                                        start, linTaskBean.getLength() - 1,
                                        linTaskBean.getLength(),
                                        linTaskBean.getAutoRetry(),
                                        linTaskBean.getCallInterval()
                                );
                                LinRunnable linRunnable = new LinRunnable(countDownLatch, linThreadBean,
                                        linTaskBean.getThreadNum() - 1);
                                linRunnableList.add(linRunnable);
                                executors.execute(linRunnable);
                            } else {
                                Log.e(TAG, "获取下载文件长度失败，请检查下载文件地址");
                            }
                        }
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    } finally {
                        try {
                            if (accessFile != null)
                                accessFile.close();
                            if (conn != null)
                                conn.disconnect();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
                //notify onStart
                status = LinDownload.STATUS_DOWNLOAD;
                handler.sendEmptyMessage(MSG_START);
                //定时更新进度
                scheduled.scheduleAtFixedRate(new Runnable() {
                    @Override
                    public void run() {
                        ListIterator<LinRunnable> iterator = linRunnableList.listIterator();
                        long finished = 0L;
                        while (iterator.hasNext()) {
                            finished += iterator.next().getFinished();
                        }
                        //更新数据库记录的任务信息
                        linTaskBean.update();
                        Message message = handler.obtainMessage(MSG_PROGRESS, finished);
                        handler.sendMessage(message);
                    }
                }, linTaskBean.getCallInterval(), linTaskBean.getCallInterval(), TimeUnit.MILLISECONDS);
                try {
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    countDownLatch = null;
                    if (status != LinDownload.STATUS_PAUSE && status != LinDownload.STATUS_CANCEL) {
                        boolean success = true;
                        for (LinRunnable linRunnable : linRunnableList) {
                            if (!linRunnable.isFinish()) {
                                success = false;
                                onFailed();
                                break;
                            }
                        }
                        if (success) {
                            handler.sendEmptyMessage(MSG_SUCCESS);
                        }
                        LinDownload.decrease();
                    }
                    executors.shutdown();
                    scheduled.shutdown();
                }
            }
        });
    }

    @Override
    public LinTaskImp pause() {
        if (status == LinDownload.STATUS_DOWNLOAD) {
            status = LinDownload.STATUS_PAUSE;
        }
        for (LinRunnable linRunnable :
                linRunnableList) {
            linRunnable.pause();
        }
        linRunnableList.clear();
        countDownLatch = null;
        scheduled.shutdown();
        LinDownload.decrease();
        handler.sendEmptyMessage(MSG_PAUSE);
        return this;
    }

    @Override
    public LinTaskImp start() {
        if (LinDownload.runTaskNumber >= LinDownload.MAX_TASK_NUMBER) {
            linTaskBean.setState(LinDownload.STATUS_WAITING);
            LinDownloadTaskImp wait = new LinDownloadTaskImp(linTaskBean, listener);
            LinDownload.waitQueue.add(wait);
            LinDownload.taskMap.remove(linTaskBean.getUrl());
            return null;
        }
        if (!(status == LinDownload.STATUS_DOWNLOAD || status == LinDownload.STATUS_FINISH)) {
            spiltThread();
            LinDownload.increase();
        }
        return this;
    }

    @Override
    public LinTaskImp cancel() {
        if (status == LinDownload.STATUS_DOWNLOAD || status == LinDownload.STATUS_PAUSE) {
            status = LinDownload.STATUS_CANCEL;
            for (LinRunnable linRunnable :
                    linRunnableList) {
                linRunnable.cancel();
            }
            linRunnableList.clear();
            countDownLatch = null;
            scheduled.shutdown();
        }
        LinDownload.taskMap.remove(linTaskBean.getUrl());
        LinDownload.decrease();
        handler.sendEmptyMessage(MSG_CANCEL);
        return this;
    }

    @Override
    public LinTaskImp name(String fileName) {
        this.linTaskBean.setFileName(fileName);
        return this;
    }

    @Override
    public LinTaskImp path(String savePath) {
        this.linTaskBean.setSavePath(savePath);
        return this;
    }

    @Override
    public LinTaskImp threadNum(int threadNum) {
        this.linTaskBean.setThreadNum(threadNum);
        return this;
    }

    @Override
    public LinTaskImp callInterval(long callInterval) {
        this.linTaskBean.setCallInterval(callInterval);
        return this;
    }

    @Override
    public LinTaskImp retryNum(int retryNum) {
        this.linTaskBean.setAutoRetry(retryNum);
        return this;
    }

    @Override
    public LinTaskImp listener(LinDownloadListener listener) {
        this.listener = listener;
        return this;
    }

    private void onFailed() {
        status = LinDownload.STATUS_FAILED;
        handler.sendEmptyMessage(MSG_FAIL);
        if (linTaskBean.getAutoRetry() > autoRetry) {
            start();
            LinDownload.increase();
            autoRetry++;
        } else {
            LinDownload.taskMap.remove(linTaskBean.getUrl());
            LinDownload.decrease();
            clear();
        }
    }

    private void clear() {
        listener = null;
        linTaskBean = null;
        linRunnableList.clear();
    }

    @SuppressLint("HandlerLeak")
    private Handler handler = new Handler() {

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            if (listener != null) {
                switch (msg.what) {
                    case MSG_START:
                        listener.onTaskStart(linTaskBean);
                        break;
                    case MSG_PROGRESS:
                        long finished = (long) msg.obj;
                        listener.onTaskProgress(linTaskBean, finished, linTaskBean.getLength(),
                                LinDownload.calculateSpeed(lastFinished, finished, linTaskBean.getCallInterval()));
                        lastFinished = finished;
                        break;
                    case MSG_SUCCESS:
                        listener.onTaskSuccess(linTaskBean);
                        LinDownload.taskMap.remove(linTaskBean.getUrl());
                        clear();
                        break;
                    case MSG_FAIL:
                        listener.onTaskFailed(linTaskBean);
                        break;
                    case MSG_PAUSE:
                        listener.onTaskPause(linTaskBean);
                        break;
                    case MSG_CANCEL:
                        listener.onTaskCancel(linTaskBean);
                        break;
                    case MSG_RETRY:
                        listener.onTaskRetry(linTaskBean);
                        break;
                }
            }
        }
    };

    public int getStatus() {
        return status;
    }

    public LinTaskBean getLinTaskBean() {
        return linTaskBean;
    }

    public LinDownloadListener getListener() {
        return listener;
    }
}
